Skip to content

initial implementation of the groth16 verifier - #4

Merged
zz-sol merged 30 commits into
solana-program:mainfrom
zz-sol:main
Sep 28, 2026
Merged

zz-sol merged 30 commits into
solana-program:mainfrom
zz-sol:main

Conversation

@zz-sol

@zz-sol zz-sol commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

#2 got merged/closed because of a forced push. reopen this PR

@zz-sol
zz-sol marked this pull request as ready for review September 17, 2026 12:18

@samkim-crypto samkim-crypto left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, some comments from my pass through the program.

Comment thread README.md
Comment thread README.md
Comment thread program/src/processor/publish.rs Outdated
Comment thread README.md Outdated
Comment thread program/src/lib.rs Outdated
Comment thread program/src/error.rs Outdated
Comment thread program/tests/registry.rs
Comment thread program/tests/common/mod.rs
Comment thread program/src/processor/publish.rs Outdated

@samkim-crypto samkim-crypto left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starting to dig into the verify library, but some initial comments below.

Comment thread groth16-verify/src/lib.rs
Comment thread groth16-verify/src/lib.rs
@vadorovsky
vadorovsky self-requested a review September 21, 2026 13:33

@vadorovsky vadorovsky left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not fully done with reviewing, but I have a couple of comments for now.

Comment thread .cargo/audit.toml Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we get rid of this issue by updating to ark-relations 0.6 (and in general, by updating all the arkworks crates)?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we used ark 0.5 for the agave and cryptography repo due to potential breaking changes, but I don't think there is a reason to stick with the old version in this repo.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 82f89fd

Comment thread README.md Outdated

## Build and test

Host toolchain is pinned to stable `1.93.1` in `rust-toolchain.toml`. SBF

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we should mention Rust versions in README. If you keep it, every time we bump the version, we end up with an additional chore of having to update README.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in cadf68e

Comment thread groth16-verify/src/instruction.rs Outdated
Comment on lines +45 to +47
let mut data = Vec::with_capacity(3);
data.push(Tag::InitializeStaging as u8);
data.extend_from_slice(&num_public_inputs.to_le_bytes());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could avoid a heap allocation and use an array instead, especially since we are dealing with 3 bytes here. 😄

Suggested change
let mut data = Vec::with_capacity(3);
data.push(Tag::InitializeStaging as u8);
data.extend_from_slice(&num_public_inputs.to_le_bytes());
let [lo, hi] = num_public_inputs.to_le_bytes();
let data = [Tag::InitializeStaging as u8, lo, hi];

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in a82c046

Comment thread groth16-verify/src/verifying_key.rs
Comment thread groth16-verify/src/vk.rs Outdated
Comment thread groth16-verify/src/syscall.rs
Comment thread groth16-verify/Cargo.toml Outdated
Comment thread program/src/processor/publish.rs Outdated
return Err(Groth16ProgramError::IdentityKeyElement.into());
}

let mut input = [0u8; 3 * PAIRING_ELEMENT_SIZE];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of initializing the array with zeros, we could either:

  • Use https://crates.io/crates/arrayvec - my preference, since it provides safe abstraction over MaybeUninit.
  • Use MaybeUninit and deal with unsafe operations ourselves.

I can come up with more specific proposal tomorrow.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good

acc = g1_add(&acc, ic)?;
} else {
let term = g1_mul(ic, s)?;
acc = g1_add(&acc, &term)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if I am misunderstanding this, but we can skip if scalar is zero, right? Maybe we should do something like

          let ic = vk.ic(i + 1);
          if scalar::is_zero(ic) {
              continue;
          }

          if scalar::is_one(s) {
              acc = g1_add(&acc, ic)?;
          } else {
              let term = g1_mul(ic, s)?;
              acc = g1_add(&acc, &term)?;
          }

@zz-sol zz-sol Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I may have misunderstood the point but zero scalars are already skipped in line 67. Is that what you meant? or did you want to change the order of doing zero scalar check and ic increment?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah sorry, my wording might have been unclear. I meant checking whether the IC point is the identity (scalar::is_zero(ic)). Even when s is nonzero, multiplying an identity point contributes nothing, so we can skip that term. The existing is_zero(s) check handles zero scalars. Am I misunderstanding the logic here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh thanks! I misread the scalars. fixed in 11b2ae7

Comment thread groth16-verify/src/validation.rs Outdated
Comment thread groth16-verify/src/instruction.rs Outdated

@samkim-crypto samkim-crypto left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the PR looks fine to me as an initial version. The high-level design makes sense to me. Obviously, there are places that can be optimized, but these can be done in follow-up PRs iteratively.

I'll give an approval when any of the outstanding comments above are all addressed.

@vadorovsky

vadorovsky commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

If you don't mind, I would like to have one more day for reviewing this. Although it's very likely that I will approve.

(If there are any deadlines / you're on rush, I can do a post-merge review.)

@zz-sol

zz-sol commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator Author

If you don't mind, I would like to have one more day for reviewing this. Although it's very likely that I will approve.

(If there are any deadlines / you're on rush, I can do a post-merge review.)

no hurries at all. take your time. thanks for the review!

@vadorovsky vadorovsky left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to merge. :shipit:

Just one non-blocking question that can be addressed later.


#[inline]
pub fn a(&self) -> &'a [u8; G1_SIZE] {
fixed(self.bytes, PROOF_A_OFFSET)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current design choice of this Proof struct is that we keep everything in one contiguous array, and then split it into a(), b(), c() etc. on demand. All these methods use fixed() to split the contiguous array into chunks and handle the size-related error.

How often do we expect to use the "chunking" methods a(), b(), c(), and how often do we expect to use as_bytes()? I feel like we are going to use the "chunking" methods way more often. If that's true, then perhaps we should consider keeping each component as a separate field, and then use wincode zero-copy serialization for as_bytes().

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On-chain, Verify calls only a_b() and c(), once each per proof, and never as_bytes(); A ‖ B is kept contiguous so pairing slot 0 is a single copy.

I feel like we are already zero-copying and perhaps we don't want to add wincode (an additional dependency)?

@samkim-crypto samkim-crypto left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@zz-sol
zz-sol merged commit 8bd06b4 into solana-program:main Sep 28, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants